﻿using System;
using System.Activities;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.TeamFoundation.VersionControl.Client;
using Microsoft.TeamFoundation.Build.Client;
using Microsoft.TeamFoundation.Build.Workflow.Design;
using Microsoft.TeamFoundation.Client;

namespace TfsCloudDeploy.Workflow.Core.Activities
{
    [Designer(typeof(TeamBuildBaseActivityDesigner)), SRDescription(typeof(ExtendedWorkflowResources), "GetLastKnownGoodBuildActivityDescription"), BuildActivity(HostEnvironmentOption.All)]
    public class GetLastKnownGoodBuild : CodeActivity<TargetBuildLabel>
    {
        [SRDescription(typeof(ExtendedWorkflowResources), "AvailableBuildsPatternDescription"), SRCategory(typeof(ExtendedWorkflowResources), "ActivityCategoryMiscellaneous"), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible), RequiredArgument]
        public InArgument<string> AvailableBuildsPattern { get; set; }

        [SRDescription(typeof(ExtendedWorkflowResources), "BuildLabelSourceControlScopeDescription"), SRCategory(typeof(ExtendedWorkflowResources), "ActivityCategoryMiscellaneous"), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public InArgument<string> BuildLabelSourceControlScope { get; set; }

        [SRDescription(typeof(ExtendedWorkflowResources), "BuildLabelOwnerNameDescription"), SRCategory(typeof(ExtendedWorkflowResources), "ActivityCategoryMiscellaneous"), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public InArgument<string> BuildLabelOwnerName { get; set; }

        [SRDescription(typeof(ExtendedWorkflowResources), "ExcludeLabelsForFailedBuildsDescription"), SRCategory(typeof(ExtendedWorkflowResources), "ActivityCategoryMiscellaneous"), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public InArgument<bool> ExcludeLabelsForFailedBuilds { get; set; }

        [SRDescription(typeof(ExtendedWorkflowResources), "AcceptableBuildQualitiesDescription"), SRCategory(typeof(ExtendedWorkflowResources), "ActivityCategoryMiscellaneous"), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public InArgument<string[]> AcceptableBuildQualities { get; set; }

        [SRDescription(typeof(ExtendedWorkflowResources), "TeamProjectNameDescription"), SRCategory(typeof(ExtendedWorkflowResources), "ActivityCategoryMiscellaneous"), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public InArgument<string> TeamProjectName { get; set; }

        protected override TargetBuildLabel Execute(CodeActivityContext context)
        {
            string buildLabelSourceControlScope = this.BuildLabelSourceControlScope.Get(context);
            string buildLabelOwnerName = this.BuildLabelOwnerName.Get(context);
            string teamProjectName = this.TeamProjectName.Get(context);
            IBuildDetail buildDetail = context.GetExtension<IBuildDetail>();

            if (string.IsNullOrEmpty(teamProjectName))
            {
                teamProjectName = buildDetail.TeamProject;
            }

            TfsTeamProjectCollection tfsProjectCollection = buildDetail.BuildServer.TeamProjectCollection;
            VersionControlServer versionControlServer = tfsProjectCollection.GetService<VersionControlServer>();

            VersionControlLabel[] availableLabels = versionControlServer.QueryLabels(
                null,
                string.IsNullOrWhiteSpace(buildLabelSourceControlScope) ? null : buildLabelSourceControlScope,
                string.IsNullOrWhiteSpace(buildLabelOwnerName) ? null : buildLabelOwnerName,
                false);

            if (availableLabels.Length > 0)
            {
                IEnumerable<VersionControlLabel> filteredLabels = Enumerable.Empty<VersionControlLabel>();
                string availableBuildsPattern = this.AvailableBuildsPattern.Get(context);

                if (!string.IsNullOrWhiteSpace(availableBuildsPattern))
                {
                    // Filter the labels by the regex pattern:
                    filteredLabels = from label in availableLabels
                                     where Regex.IsMatch(label.Name, availableBuildsPattern, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Singleline)
                                     select label;
                }
                else
                {
                    filteredLabels = availableLabels.AsEnumerable<VersionControlLabel>();
                }

                if (filteredLabels.Count() > 0)
                {
                    string[] acceptableBuildQualities = this.AcceptableBuildQualities.Get(context);
                    bool excludeLabelsForFailedBuilds = this.ExcludeLabelsForFailedBuilds.Get(context);

                    if (excludeLabelsForFailedBuilds || (acceptableBuildQualities != null && acceptableBuildQualities.Length > 0))
                    {
                        // Get the builds corresponding to the labels. Assumes that standard DefaultTemplate labeling takes place where the build label is the same as the build number.
                        IBuildServer buildServer = buildDetail.BuildServer;
                        IBuildDetailSpec buildDetailSpec = buildServer.CreateBuildDetailSpec(teamProjectName);
                        IBuildQueryResult buildQueryResult = buildServer.QueryBuilds(buildDetailSpec);

                        if (buildQueryResult.Builds.Length > 0)
                        {
                            // Add builds that do not match the conditions so they can be excluded.
                            // 1. Exclude failed builds if requested.
                            // 2. Filter by build qualities.
                            IEnumerable<IBuildDetail> exclusionBuildDetails = buildQueryResult.Builds.Where((build) =>
                            {
                                if (excludeLabelsForFailedBuilds && build.Status == BuildStatus.Failed)
                                    return true;
                                else if (AcceptableBuildQualities != null && acceptableBuildQualities.Length > 0 && !acceptableBuildQualities.Contains(build.Quality, StringComparer.InvariantCultureIgnoreCase))
                                    return true;

                                return false;
                            });

                            if (exclusionBuildDetails.Count() > 0)
                            {
                                // Only keep the labels that are not in the exclusion list.
                                filteredLabels = from label in filteredLabels
                                                 where !ContainsMatchingLabelInBuildDetails(exclusionBuildDetails, label.Name)
                                                 select label;
                            }
                        }
                    }

                    if (filteredLabels.Count() > 0)
                    {
                        // Then sort descending by LastModifiedDate and take the top element.
                        VersionControlLabel lastKnowGoodLabel = filteredLabels.OrderByDescending((label) => { return label.LastModifiedDate; }).First();
                        if (lastKnowGoodLabel != null)
                        {
                            return new TargetBuildLabel() { LabelName = lastKnowGoodLabel.Name };
                        }
                    }
                }
            }

            return null;
        }

        private bool ContainsMatchingLabelInBuildDetails(IEnumerable<IBuildDetail> exclusionBuildDetails, string labelName)
        {
            if (exclusionBuildDetails.Count() > 0)
            {
                foreach (var buildDetail in exclusionBuildDetails)
                {
                    if (string.Compare(buildDetail.BuildNumber, labelName, true) == 0)
                        return true;
                }
            }
            return false;
        }
    }
}
